CodeMirror.defineMode("smarty", function (config, parserConfig) {
    var keyFuncs = ["debug", "extends", "function", "include", "literal"];
    var last;
    var regs = {
        operatorChars: /[+\-*&%=<>!?]/,
        validIdentifier: /[a-zA-Z0-9\_]/,
        stringChar: /[\'\"]/
    }
    var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{";
    var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}";

    function ret(style, lst) {
        last = lst;
        return style;
    }


    function tokenizer(stream, state) {
        function chain(parser) {
            state.tokenize = parser;
            return parser(stream, state);
        }

        if (stream.match(leftDelim, true)) {
            if (stream.eat("*")) {
                return chain(inBlock("comment", "*" + rightDelim));
            }
            else {
                state.tokenize = inSmarty;
                return "tag";
            }
        }
        else {
            // I'd like to do an eatWhile() here, but I can't get it to eat only up to the rightDelim string/char
            stream.next();
            return null;
        }
    }

    function inSmarty(stream, state) {
        if (stream.match(rightDelim, true)) {
            state.tokenize = tokenizer;
            return ret("tag", null);
        }

        var ch = stream.next();
        if (ch == "$") {
            stream.eatWhile(regs.validIdentifier);
            return ret("variable-2", "variable");
        }
        else if (ch == ".") {
            return ret("operator", "property");
        }
        else if (regs.stringChar.test(ch)) {
            state.tokenize = inAttribute(ch);
            return ret("string", "string");
        }
        else if (regs.operatorChars.test(ch)) {
            stream.eatWhile(regs.operatorChars);
            return ret("operator", "operator");
        }
        else if (ch == "[" || ch == "]") {
            return ret("bracket", "bracket");
        }
        else if (/\d/.test(ch)) {
            stream.eatWhile(/\d/);
            return ret("number", "number");
        }
        else {
            if (state.last == "variable") {
                if (ch == "@") {
                    stream.eatWhile(regs.validIdentifier);
                    return ret("property", "property");
                }
                else if (ch == "|") {
                    stream.eatWhile(regs.validIdentifier);
                    return ret("qualifier", "modifier");
                }
            }
            else if (state.last == "whitespace") {
                stream.eatWhile(regs.validIdentifier);
                return ret("attribute", "modifier");
            }
            else if (state.last == "property") {
                stream.eatWhile(regs.validIdentifier);
                return ret("property", null);
            }
            else if (/\s/.test(ch)) {
                last = "whitespace";
                return null;
            }

            var str = "";
            if (ch != "/") {
                str += ch;
            }
            var c = "";
            while ((c = stream.eat(regs.validIdentifier))) {
                str += c;
            }
            var i, j;
            for (i = 0, j = keyFuncs.length; i < j; i++) {
                if (keyFuncs[i] == str) {
                    return ret("keyword", "keyword");
                }
            }
            if (/\s/.test(ch)) {
                return null;
            }
            return ret("tag", "tag");
        }
    }

    function inAttribute(quote) {
        return function (stream, state) {
            while (!stream.eol()) {
                if (stream.next() == quote) {
                    state.tokenize = inSmarty;
                    break;
                }
            }
            return "string";
        };
    }

    function inBlock(style, terminator) {
        return function (stream, state) {
            while (!stream.eol()) {
                if (stream.match(terminator)) {
                    state.tokenize = tokenizer;
                    break;
                }
                stream.next();
            }
            return style;
        };
    }

    return {
        startState: function () {
            return {tokenize: tokenizer, mode: "smarty", last: null};
        },
        token: function (stream, state) {
            var style = state.tokenize(stream, state);
            state.last = last;
            return style;
        },
        electricChars: ""
    }
});

CodeMirror.defineMIME("text/x-smarty", "smarty");